<!DOCTYPE html>
<html lang=zh>
<head>
  <meta charset="utf-8">
  
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
  <meta name="renderer" content="webkit">
  <meta http-equiv="Cache-Control" content="no-transform" />
  <meta http-equiv="Cache-Control" content="no-siteapp" />
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="format-detection" content="telephone=no,email=no,adress=no">
  <!-- Color theme for statusbar -->
  <meta name="theme-color" content="#000000" />
  <!-- 强制页面在当前窗口以独立页面显示,防止别人在框架里调用页面 -->
  <meta http-equiv="window-target" content="_top" />
  
  
  <title>NIO | Skyung-Blog</title>
  <meta name="description" content="non-blocking io 非阻塞 IO 1. 三大组件1.1 Channel &amp; Bufferchannel 有一点类似于 stream，它就是读写数据的双向通道，可以从 channel 将数据读入 buffer，也可以将 buffer 的数据写入 channel，而之前的 stream 要么是输入，要么是输出，channel 比 stream 更为底层 123graph LRcha">
<meta property="og:type" content="article">
<meta property="og:title" content="NIO">
<meta property="og:url" content="https://skyung.gitee.io/Skyung/2022/04/24/NIO/index.html">
<meta property="og:site_name" content="Skyung-Blog">
<meta property="og:description" content="non-blocking io 非阻塞 IO 1. 三大组件1.1 Channel &amp; Bufferchannel 有一点类似于 stream，它就是读写数据的双向通道，可以从 channel 将数据读入 buffer，也可以将 buffer 的数据写入 channel，而之前的 stream 要么是输入，要么是输出，channel 比 stream 更为底层 123graph LRcha">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420170803926.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171053159.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171145631.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171204946.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171227259.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171511579.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422142959581.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422190953485.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191022496.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191037701.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191057656.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191118224.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191137181.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191147599.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422193433564.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422193450515.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422193512663.png">
<meta property="og:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422200545550.png">
<meta property="article:published_time" content="2022-04-23T16:00:00.000Z">
<meta property="article:modified_time" content="2022-04-23T16:00:00.000Z">
<meta property="article:author" content="Skyung">
<meta property="article:tag" content="java">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420170803926.png">
  <!-- Canonical links -->
  <link rel="canonical" href="https://skyung.gitee.io/Skyung/2022/04/24/NIO/index.html">
  
    <link rel="alternate" href="/atom.xml" title="Skyung-Blog" type="application/atom+xml">
  
  
    <link rel="icon" href="images/favicon.png" type="image/x-icon">
  
  
<link rel="stylesheet" href="/Skyung/css/style.css">

  
  
  
  
<meta name="generator" content="Hexo 5.4.1"></head>


<body class="main-center" itemscope itemtype="http://schema.org/WebPage">
  <header class="header" itemscope itemtype="http://schema.org/WPHeader">
  <div class="slimContent">
    <div class="navbar-header">
      
      
      <div class="profile-block text-center">
        <a id="avatar" href="https://github.com/cofess" target="_blank">
          <img class="img-circle img-rotate" src="/Skyung/images/avatar.jpg" width="200" height="200">
        </a>
        <h2 id="name" class="hidden-xs hidden-sm">Skyung</h2>
        <h3 id="title" class="hidden-xs hidden-sm hidden-md">Web Developer</h3>
        <small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> Wuhan, China</small>
      </div>
      
      <div class="search" id="search-form-wrap">

    <form class="search-form sidebar-form">
        <div class="input-group">
            <input type="text" class="search-form-input form-control" placeholder="搜索" />
            <span class="input-group-btn">
                <button type="submit" class="search-form-submit btn btn-flat" onclick="return false;"><i class="icon icon-search"></i></button>
            </span>
        </div>
    </form>
    <div class="ins-search">
  <div class="ins-search-mask"></div>
  <div class="ins-search-container">
    <div class="ins-input-wrapper">
      <input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech />
      <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
    </div>
    <div class="ins-section-wrapper">
      <div class="ins-section-container"></div>
    </div>
  </div>
</div>


</div>
      <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
    </div>
    <nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation">
      <ul class="nav navbar-nav main-nav ">
        
        
        <li class="menu-item menu-item-home">
          <a href="/Skyung/.">
            
            <i class="icon icon-home-fill"></i>
            
            <span class="menu-title">首页</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-archives">
          <a href="/Skyung/archives">
            
            <i class="icon icon-archives-fill"></i>
            
            <span class="menu-title">归档</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-categories">
          <a href="/Skyung/categories">
            
            <i class="icon icon-folder"></i>
            
            <span class="menu-title">分类</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-tags">
          <a href="/Skyung/tags">
            
            <i class="icon icon-tags"></i>
            
            <span class="menu-title">标签</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-repository">
          <a href="/Skyung/repository">
            
            <i class="icon icon-project"></i>
            
            <span class="menu-title">项目</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-books">
          <a href="/Skyung/books">
            
            <i class="icon icon-book-fill"></i>
            
            <span class="menu-title">书单</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-links">
          <a href="/Skyung/links">
            
            <i class="icon icon-friendship"></i>
            
            <span class="menu-title">友链</span>
          </a>
        </li>
        
        
        <li class="menu-item menu-item-about">
          <a href="/Skyung/about">
            
            <i class="icon icon-cup-fill"></i>
            
            <span class="menu-title">关于</span>
          </a>
        </li>
        
      </ul>
      
	
    <ul class="social-links">
    	
        <li><a href="/Skyung/null" target="_blank" title="Github" data-toggle=tooltip data-placement=top><i class="icon icon-github"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Weibo" data-toggle=tooltip data-placement=top><i class="icon icon-weibo"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Twitter" data-toggle=tooltip data-placement=top><i class="icon icon-twitter"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Behance" data-toggle=tooltip data-placement=top><i class="icon icon-behance"></i></a></li>
        
        <li><a href="/Skyung/atom.xml" target="_blank" title="Rss" data-toggle=tooltip data-placement=top><i class="icon icon-rss"></i></a></li>
        
    </ul>

    </nav>
  </div>
</header>

  
    <aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    
      <div class="widget">
    <h3 class="widget-title">公告</h3>
    <div class="widget-body">
        <div id="board">
            <div class="content">
                <p>欢迎交流与分享经验!</p>
            </div>
        </div>
    </div>
</div>

    
      
  <div class="widget">
    <h3 class="widget-title">分类</h3>
    <div class="widget-body">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a><span class="category-list-count">20</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title">标签</h3>
    <div class="widget-body">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/java/" rel="tag">java</a><span class="tag-list-count">19</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/jvm/" rel="tag">jvm</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/linux/" rel="tag">linux</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" rel="tag">多线程</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" rel="tag">并发编程</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" rel="tag">设计模式</a><span class="tag-list-count">4</span></li><li class="tag-list-item"><a class="tag-list-link" href="/Skyung/tags/%E9%9B%B6%E6%95%A3%E7%82%B9/" rel="tag">零散点</a><span class="tag-list-count">1</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title">标签云</h3>
    <div class="widget-body tagcloud">
      <a href="/Skyung/tags/java/" style="font-size: 14px;">java</a> <a href="/Skyung/tags/jvm/" style="font-size: 13px;">jvm</a> <a href="/Skyung/tags/linux/" style="font-size: 13px;">linux</a> <a href="/Skyung/tags/redis/" style="font-size: 13px;">redis</a> <a href="/Skyung/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" style="font-size: 13px;">多线程</a> <a href="/Skyung/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/" style="font-size: 13.33px;">并发编程</a> <a href="/Skyung/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size: 13.67px;">设计模式</a> <a href="/Skyung/tags/%E9%9B%B6%E6%95%A3%E7%82%B9/" style="font-size: 13px;">零散点</a>
    </div>
  </div>

    
      
  <div class="widget">
    <h3 class="widget-title">归档</h3>
    <div class="widget-body">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/05/">五月 2022</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/04/">四月 2022</a><span class="archive-list-count">7</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/03/">三月 2022</a><span class="archive-list-count">9</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2022/02/">二月 2022</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/Skyung/archives/2021/09/">九月 2021</a><span class="archive-list-count">1</span></li></ul>
    </div>
  </div>


    
      
  <div class="widget">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget-body">
      <ul class="recent-post-list list-unstyled no-thumbnail">
        
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/08/Spring/" class="title">spring</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-07T16:00:00.000Z" itemprop="datePublished">2022-05-08</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/08/netty-RPC/" class="title">Netty-RPC</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-07T16:00:00.000Z" itemprop="datePublished">2022-05-08</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/07/%E5%B8%B8%E7%94%A8%E7%B1%BB%E5%BA%93%E5%92%8CAPI/" class="title">常见类库</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-06T16:00:00.000Z" itemprop="datePublished">2022-05-07</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/07/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/" class="title">单元测试</a>
              </p>
              <p class="item-date">
                <time datetime="2022-05-06T16:00:00.000Z" itemprop="datePublished">2022-05-07</time>
              </p>
            </div>
          </li>
          
          <li>
            
            <div class="item-inner">
              <p class="item-category">
                <a class="category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
              </p>
              <p class="item-title">
                <a href="/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/" class="title">Netty聊天室</a>
              </p>
              <p class="item-date">
                <time datetime="2022-04-30T16:00:00.000Z" itemprop="datePublished">2022-05-01</time>
              </p>
            </div>
          </li>
          
      </ul>
    </div>
  </div>
  

    
  </div>
</aside>

  
  
  <aside class="sidebar sidebar-toc collapse   in  " id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar">
  <div class="slimContent">
    <nav id="toc" class="article-toc">
      <h3 class="toc-title">文章目录</h3>
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E4%B8%89%E5%A4%A7%E7%BB%84%E4%BB%B6"><span class="toc-number">1.</span> <span class="toc-text">1. 三大组件</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-1-Channel-amp-Buffer"><span class="toc-number">1.1.</span> <span class="toc-text">1.1 Channel &amp; Buffer</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-2-Selector"><span class="toc-number">1.2.</span> <span class="toc-text">1.2 Selector</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%89%88%E8%AE%BE%E8%AE%A1"><span class="toc-number">1.2.1.</span> <span class="toc-text">多线程版设计</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%89%88%E8%AE%BE%E8%AE%A1"><span class="toc-number">1.2.2.</span> <span class="toc-text">线程池版设计</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#selector-%E7%89%88%E8%AE%BE%E8%AE%A1"><span class="toc-number">1.2.3.</span> <span class="toc-text">selector 版设计</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-ByteBuffer"><span class="toc-number">2.</span> <span class="toc-text">2. ByteBuffer</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-ByteBuffer-%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E5%A7%BF%E5%8A%BF"><span class="toc-number">2.1.</span> <span class="toc-text">2.1  ByteBuffer 正确使用姿势</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-ByteBuffer-%E7%BB%93%E6%9E%84"><span class="toc-number">2.2.</span> <span class="toc-text">2.2 ByteBuffer 结构</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3-ByteBuffer-%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95"><span class="toc-number">2.3.</span> <span class="toc-text">2.3 ByteBuffer 常见方法</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%88%86%E9%85%8D%E7%A9%BA%E9%97%B4"><span class="toc-number">2.3.1.</span> <span class="toc-text">分配空间</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%90%91-buffer-%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE"><span class="toc-number">2.3.2.</span> <span class="toc-text">向 buffer 写入数据</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BB%8E-buffer-%E8%AF%BB%E5%8F%96%E6%95%B0%E6%8D%AE"><span class="toc-number">2.3.3.</span> <span class="toc-text">从 buffer 读取数据</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#mark-%E5%92%8C-reset"><span class="toc-number">2.3.4.</span> <span class="toc-text">mark 和 reset</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%8E-ByteBuffer-%E4%BA%92%E8%BD%AC"><span class="toc-number">2.3.5.</span> <span class="toc-text">字符串与 ByteBuffer 互转</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Buffer-%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8"><span class="toc-number">2.3.6.</span> <span class="toc-text">Buffer 的线程安全</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-4-Scattering-Reads"><span class="toc-number">2.4.</span> <span class="toc-text">2.4 Scattering Reads</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-5-Gathering-Writes"><span class="toc-number">2.5.</span> <span class="toc-text">2.5 Gathering Writes</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-6-%E9%BB%8F%E5%8C%85%EF%BC%8C%E5%8D%8A%E5%8C%85"><span class="toc-number">2.6.</span> <span class="toc-text">2.6 黏包，半包</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E6%96%87%E4%BB%B6%E7%BC%96%E7%A8%8B"><span class="toc-number">3.</span> <span class="toc-text">3. 文件编程</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-FileChannel"><span class="toc-number">3.1.</span> <span class="toc-text">3.1 FileChannel</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#FileChannel-%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F"><span class="toc-number">3.1.1.</span> <span class="toc-text">FileChannel 工作模式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96"><span class="toc-number">3.1.2.</span> <span class="toc-text">获取</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%AF%BB%E5%8F%96"><span class="toc-number">3.1.3.</span> <span class="toc-text">读取</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%86%99%E5%85%A5"><span class="toc-number">3.1.4.</span> <span class="toc-text">写入</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%85%B3%E9%97%AD"><span class="toc-number">3.1.5.</span> <span class="toc-text">关闭</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%8D%E7%BD%AE"><span class="toc-number">3.1.6.</span> <span class="toc-text">位置</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A4%A7%E5%B0%8F"><span class="toc-number">3.1.7.</span> <span class="toc-text">大小</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BC%BA%E5%88%B6%E5%86%99%E5%85%A5"><span class="toc-number">3.1.8.</span> <span class="toc-text">强制写入</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2-%E4%B8%A4%E4%B8%AA-Channel-%E4%BC%A0%E8%BE%93%E6%95%B0%E6%8D%AE"><span class="toc-number">3.2.</span> <span class="toc-text">3.2 两个 Channel 传输数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3-Path"><span class="toc-number">3.3.</span> <span class="toc-text">3.3 Path</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-4-Files"><span class="toc-number">3.4.</span> <span class="toc-text">3.4 Files</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B"><span class="toc-number">4.</span> <span class="toc-text">4. 网络编程</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#4-1-%E9%9D%9E%E9%98%BB%E5%A1%9E-vs-%E9%98%BB%E5%A1%9E"><span class="toc-number">4.1.</span> <span class="toc-text">4.1 非阻塞 vs 阻塞</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%98%BB%E5%A1%9E"><span class="toc-number">4.1.1.</span> <span class="toc-text">阻塞</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%9D%9E%E9%98%BB%E5%A1%9E"><span class="toc-number">4.1.2.</span> <span class="toc-text">非阻塞</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8"><span class="toc-number">4.1.3.</span> <span class="toc-text">多路复用</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-2-Selector"><span class="toc-number">4.2.</span> <span class="toc-text">4.2 Selector</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%88%9B%E5%BB%BA"><span class="toc-number">4.2.1.</span> <span class="toc-text">创建</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%BB%91%E5%AE%9A-Channel-%E4%BA%8B%E4%BB%B6"><span class="toc-number">4.2.2.</span> <span class="toc-text">绑定 Channel 事件</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%9B%91%E5%90%AC-Channel-%E4%BA%8B%E4%BB%B6"><span class="toc-number">4.2.3.</span> <span class="toc-text">监听 Channel 事件</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#select-%E4%BD%95%E6%97%B6%E4%B8%8D%E9%98%BB%E5%A1%9E"><span class="toc-number">4.2.4.</span> <span class="toc-text">select 何时不阻塞</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-3-%E5%A4%84%E7%90%86-accept-%E4%BA%8B%E4%BB%B6"><span class="toc-number">4.3.</span> <span class="toc-text">4.3 处理 accept 事件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-4-%E5%A4%84%E7%90%86-read-%E4%BA%8B%E4%BB%B6"><span class="toc-number">4.4.</span> <span class="toc-text">4.4 处理 read 事件</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%A4%84%E7%90%86%E6%B6%88%E6%81%AF%E7%9A%84%E8%BE%B9%E7%95%8C"><span class="toc-number">4.4.1.</span> <span class="toc-text">处理消息的边界</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#ByteBuffer-%E5%A4%A7%E5%B0%8F%E5%88%86%E9%85%8D"><span class="toc-number">4.4.2.</span> <span class="toc-text">ByteBuffer 大小分配</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-5-%E5%A4%84%E7%90%86-write-%E4%BA%8B%E4%BB%B6"><span class="toc-number">4.5.</span> <span class="toc-text">4.5 处理 write 事件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-6-%E6%9B%B4%E8%BF%9B%E4%B8%80%E6%AD%A5"><span class="toc-number">4.6.</span> <span class="toc-text">4.6 更进一步</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%88%A9%E7%94%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%BC%98%E5%8C%96"><span class="toc-number">4.6.1.</span> <span class="toc-text">利用多线程优化</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-7-UDP"><span class="toc-number">4.7.</span> <span class="toc-text">4.7 UDP</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-NIO-vs-BIO"><span class="toc-number">5.</span> <span class="toc-text">5. NIO vs BIO</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-stream-vs-channel"><span class="toc-number">5.1.</span> <span class="toc-text">5.1 stream vs channel</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-IO-%E6%A8%A1%E5%9E%8B"><span class="toc-number">5.2.</span> <span class="toc-text">5.2 IO 模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-%E9%9B%B6%E6%8B%B7%E8%B4%9D"><span class="toc-number">5.3.</span> <span class="toc-text">5.3 零拷贝</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%A0%E7%BB%9F-IO-%E9%97%AE%E9%A2%98"><span class="toc-number">5.3.1.</span> <span class="toc-text">传统 IO 问题</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#NIO-%E4%BC%98%E5%8C%96"><span class="toc-number">5.3.2.</span> <span class="toc-text">NIO 优化</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-AIO"><span class="toc-number">5.4.</span> <span class="toc-text">5.4 AIO</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%87%E4%BB%B6-AIO"><span class="toc-number">5.4.1.</span> <span class="toc-text">文件 AIO</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%BD%91%E7%BB%9C-AIO"><span class="toc-number">5.4.2.</span> <span class="toc-text">网络 AIO</span></a></li></ol></li></ol></li></ol>
    </nav>
  </div>
</aside>

<main class="main" role="main">
  <div class="content">
  <article id="post-NIO" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting">
    
    <div class="article-header">
      
        
  
    <h1 class="article-title" itemprop="name">
      NIO
    </h1>
  

      
      <div class="article-meta">
        <span class="article-date">
    <i class="icon icon-calendar-check"></i>
	<a href="/Skyung/2022/04/24/NIO/" class="article-date">
	  <time datetime="2022-04-23T16:00:00.000Z" itemprop="datePublished">2022-04-24</time>
	</a>
</span>
        
  <span class="article-category">
    <i class="icon icon-folder"></i>
    <a class="article-category-link" href="/Skyung/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a>
  </span>

        
  <span class="article-tag">
    <i class="icon icon-tags"></i>
	<a class="article-tag-link-link" href="/Skyung/tags/java/" rel="tag">java</a>
  </span>


        

        <span class="post-comment"><i class="icon icon-comment"></i> <a href="/Skyung/2022/04/24/NIO/#comments" class="article-comment-link">评论</a></span>
        
      </div>
    </div>
    <div class="article-entry marked-body" itemprop="articleBody">
      
        <p>non-blocking io 非阻塞 IO</p>
<h2 id="1-三大组件"><a href="#1-三大组件" class="headerlink" title="1. 三大组件"></a>1. 三大组件</h2><h3 id="1-1-Channel-amp-Buffer"><a href="#1-1-Channel-amp-Buffer" class="headerlink" title="1.1 Channel &amp; Buffer"></a>1.1 Channel &amp; Buffer</h3><p>channel 有一点类似于 stream，它就是读写数据的<strong>双向通道</strong>，可以从 channel 将数据读入 buffer，也可以将 buffer 的数据写入 channel，而之前的 stream 要么是输入，要么是输出，channel 比 stream 更为底层</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">graph LR</span><br><span class="line">channel --&gt; buffer</span><br><span class="line">buffer --&gt; channel</span><br></pre></td></tr></table></figure>

<p>常见的 Channel 有</p>
<ul>
<li>FileChannel</li>
<li>DatagramChannel（UDP)</li>
<li>SocketChannel</li>
<li>ServerSocketChannel</li>
</ul>
<p>buffer 则用来缓冲读写数据，常见的 buffer 有</p>
<ul>
<li>ByteBuffer（常用）<ul>
<li>MappedByteBuffer</li>
<li>DirectByteBuffer</li>
<li>HeapByteBuffer</li>
</ul>
</li>
<li>ShortBuffer</li>
<li>IntBuffer</li>
<li>LongBuffer</li>
<li>FloatBuffer</li>
<li>DoubleBuffer</li>
<li>CharBuffer</li>
</ul>
<h3 id="1-2-Selector"><a href="#1-2-Selector" class="headerlink" title="1.2 Selector"></a>1.2 Selector</h3><p>selector 单从字面意思不好理解，需要结合服务器的设计演化来理解它的用途</p>
<h4 id="多线程版设计"><a href="#多线程版设计" class="headerlink" title="多线程版设计"></a>多线程版设计</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">subgraph 多线程版</span><br><span class="line">t1(thread) --&gt; s1(socket1)</span><br><span class="line">t2(thread) --&gt; s2(socket2)</span><br><span class="line">t3(thread) --&gt; s3(socket3)</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<p> 多线程版缺点</p>
<ul>
<li>内存占用高</li>
<li>线程上下文切换成本高，线程多cpu需要切换</li>
<li>只适合连接数少的场景</li>
</ul>
<h4 id="线程池版设计"><a href="#线程池版设计" class="headerlink" title="线程池版设计"></a>线程池版设计</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">subgraph 线程池版</span><br><span class="line">t4(thread) --&gt; s4(socket1)</span><br><span class="line">t5(thread) --&gt; s5(socket2)</span><br><span class="line">t4(thread) -.-&gt; s6(socket3)</span><br><span class="line">t5(thread) -.-&gt; s7(socket4)</span><br><span class="line">end</span><br></pre></td></tr></table></figure>
<p> 线程池版缺点</p>
<ul>
<li>阻塞模式下，线程仅能处理一个 socket 连接</li>
<li>仅适合短连接场景</li>
</ul>
<h4 id="selector-版设计"><a href="#selector-版设计" class="headerlink" title="selector 版设计"></a>selector 版设计</h4><p>selector 的作用就是配合<strong>一个线程来管理多个 channel</strong>，获取这些 channel 上发生的事件，这些 channel 工作在<strong>非阻塞</strong>模式下，不会让线程吊死在一个 channel 上。适合连接数特别多，但流量低的场景（low traffic）</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">graph TD</span><br><span class="line">subgraph selector 版</span><br><span class="line">thread --&gt; selector</span><br><span class="line">selector --&gt; c1(channel)</span><br><span class="line">selector --&gt; c2(channel)</span><br><span class="line">selector --&gt; c3(channel)</span><br><span class="line">end</span><br></pre></td></tr></table></figure>

<p>调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件，这些事件发生，select 方法就会返回这些事件交给 thread 来处理</p>
<h2 id="2-ByteBuffer"><a href="#2-ByteBuffer" class="headerlink" title="2. ByteBuffer"></a>2. ByteBuffer</h2><p>有一普通文本文件 data.txt，内容为</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">1234567890abcd</span><br></pre></td></tr></table></figure>

<p>使用 FileChannel 来读取文件内容</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">&quot;helloword/data.txt&quot;</span>, <span class="string">&quot;rw&quot;</span>)) &#123;</span><br><span class="line">            <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> file.getChannel();</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">10</span>);</span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                <span class="comment">// 向 buffer 写入</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> channel.read(buffer);</span><br><span class="line">                log.debug(<span class="string">&quot;读到字节数：&#123;&#125;&quot;</span>, len);</span><br><span class="line">                <span class="keyword">if</span> (len == -<span class="number">1</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 切换 buffer 读模式</span></span><br><span class="line">                buffer.flip();</span><br><span class="line">                <span class="keyword">while</span>(buffer.hasRemaining()) &#123;<span class="comment">//是否还有剩余未读数据</span></span><br><span class="line">                    log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, (<span class="type">char</span>)buffer.get());</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 切换 buffer 写模式</span></span><br><span class="line">                buffer.clear();</span><br><span class="line">            &#125; <span class="keyword">while</span> (<span class="literal">true</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>.twr自动关闭流</p>
</blockquote>
<h3 id="2-1-ByteBuffer-正确使用姿势"><a href="#2-1-ByteBuffer-正确使用姿势" class="headerlink" title="2.1  ByteBuffer 正确使用姿势"></a>2.1  ByteBuffer 正确使用姿势</h3><ol>
<li>向 buffer 写入数据，例如调用 channel.read(buffer)</li>
<li>调用 flip() 切换至<strong>读模式</strong></li>
<li>从 buffer 读取数据，例如调用 buffer.get()</li>
<li>调用 clear() 或 compact() 切换至<strong>写模式</strong></li>
<li>重复 1~4 步骤</li>
</ol>
<h3 id="2-2-ByteBuffer-结构"><a href="#2-2-ByteBuffer-结构" class="headerlink" title="2.2 ByteBuffer 结构"></a>2.2 ByteBuffer 结构</h3><p>ByteBuffer 有以下重要属性</p>
<ul>
<li>capacity</li>
<li>position</li>
<li>limit</li>
</ul>
<p>一开始</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420170803926.png" alt="image-20220420170803926"></p>
<p>写模式下，position 是写入位置，limit 等于容量，下图表示写入了 4 个字节后的状态</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171053159.png" alt="image-20220420171053159"></p>
<p>flip 动作发生后，position 切换为读取位置，limit 切换为读取限制</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171145631.png" alt="image-20220420171145631"></p>
<p>读取 4 个字节后，状态</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171204946.png" alt="image-20220420171204946"></p>
<p>clear 动作发生后，状态</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171227259.png" alt="image-20220420171227259"></p>
<p>compact 方法，是把未读完的部分向前压缩，然后切换至写模式</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220420171511579.png" alt="image-20220420171511579"></p>
<h3 id="2-3-ByteBuffer-常见方法"><a href="#2-3-ByteBuffer-常见方法" class="headerlink" title="2.3 ByteBuffer 常见方法"></a>2.3 ByteBuffer 常见方法</h3><h4 id="分配空间"><a href="#分配空间" class="headerlink" title="分配空间"></a>分配空间</h4><p>可以使用 allocate 方法为 ByteBuffer 分配空间，其它 buffer 类也有该方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Bytebuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);<span class="comment">//java.nio.HeapByteBuffer</span></span><br><span class="line"><span class="type">Bytebuffer</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBuffer.allocateDirect(<span class="number">16</span>);<span class="comment">//java.nio.DirectByteBuffer</span></span><br></pre></td></tr></table></figure>

<ul>
<li><p>HeapByteBuffer堆内存，读写效率低，受GC的影响</p>
</li>
<li><p>DirectByteBuffer直接内存，读写效率高（少一次拷贝），不会受GC影响，但是分配内存的效率低，使用不当容易内存泄漏</p>
</li>
</ul>
<h4 id="向-buffer-写入数据"><a href="#向-buffer-写入数据" class="headerlink" title="向 buffer 写入数据"></a>向 buffer 写入数据</h4><p>有两种办法</p>
<ul>
<li>调用 channel 的 read 方法</li>
<li>调用 buffer 自己的 put 方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">readBytes</span> <span class="operator">=</span> channel.read(buf);</span><br><span class="line">buf.put((<span class="type">byte</span>)<span class="number">127</span>);</span><br></pre></td></tr></table></figure>

<h4 id="从-buffer-读取数据"><a href="#从-buffer-读取数据" class="headerlink" title="从 buffer 读取数据"></a>从 buffer 读取数据</h4><p>同样有两种办法</p>
<ul>
<li>调用 channel 的 write 方法</li>
<li>调用 buffer 自己的 get 方法</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">writeBytes</span> <span class="operator">=</span> channel.write(buf);</span><br><span class="line"><span class="type">byte</span> <span class="variable">b</span> <span class="operator">=</span> buf.get();</span><br></pre></td></tr></table></figure>

<p>get 方法会让 position 读指针向后走，如果想重复读取数据</p>
<ul>
<li>可以调用 rewind 方法将 position 重新置为 0</li>
<li>或者调用 get(int i) 方法获取索引 i 的内容，它<strong>不会移动读指针</strong></li>
</ul>
<h4 id="mark-和-reset"><a href="#mark-和-reset" class="headerlink" title="mark 和 reset"></a>mark 和 reset</h4><p>mark 是在读取时，做一个标记，即使 position 改变，只要调用 reset 就能回到 mark 的位置</p>
<blockquote>
<p><strong>注意</strong></p>
<p>rewind 和 flip 都会清除 mark 位置</p>
</blockquote>
<h4 id="字符串与-ByteBuffer-互转"><a href="#字符串与-ByteBuffer-互转" class="headerlink" title="字符串与 ByteBuffer 互转"></a>字符串与 ByteBuffer 互转</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1.字符串转为byte数组，直接写入</span></span><br><span class="line"><span class="type">Bytebuffer</span> <span class="variable">buffer1</span>  <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line">buffer1.put(<span class="string">&quot;hello&quot;</span>.getBytes());</span><br><span class="line"></span><br><span class="line"><span class="comment">//2.Charset,会自动切换到读模式</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer2</span> <span class="operator">=</span> StandardCharsets.UTF_8.encode(<span class="string">&quot;你好&quot;</span>);</span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer2</span> <span class="operator">=</span> Charset.forName(<span class="string">&quot;utf-8&quot;</span>).encode(<span class="string">&quot;你好&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//3.wrap，会自动切换到读模式</span></span><br><span class="line"><span class="type">Bytebuffer</span> <span class="variable">buffer3</span> <span class="operator">=</span> ByteBuffer.wrap(<span class="string">&quot;hello&quot;</span>.getBytes());</span><br><span class="line"></span><br><span class="line"><span class="comment">//ByteBuffer转String,要注意从读模式转</span></span><br><span class="line"><span class="type">CharBuffer</span> <span class="variable">buffer4</span> <span class="operator">=</span> StandardCharsets.UTF_8.decode(buffer2);</span><br><span class="line">System.out.println(buffer4.toString());</span><br></pre></td></tr></table></figure>

<h4 id="Buffer-的线程安全"><a href="#Buffer-的线程安全" class="headerlink" title="Buffer 的线程安全"></a>Buffer 的线程安全</h4><p>Buffer 是<strong>非线程安全的</strong></p>
<h3 id="2-4-Scattering-Reads"><a href="#2-4-Scattering-Reads" class="headerlink" title="2.4 Scattering Reads"></a>2.4 Scattering Reads</h3><p>分散读取</p>
<p>有一个文本文件 3parts.txt</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">onetwothree</span><br></pre></td></tr></table></figure>

<p>使用如下方式读取，可以将数据填充至多个 buffer</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> (<span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">&quot;helloword/3parts.txt&quot;</span>, <span class="string">&quot;rw&quot;</span>)) &#123;</span><br><span class="line">    <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> file.getChannel();</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">a</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">3</span>);</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">b</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">3</span>);</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">c</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">5</span>);</span><br><span class="line">    channel.read(<span class="keyword">new</span> <span class="title class_">ByteBuffer</span>[]&#123;a, b, c&#125;);</span><br><span class="line">    a.flip();</span><br><span class="line">    b.flip();</span><br><span class="line">    c.flip();</span><br><span class="line">    debug(a);</span><br><span class="line">    debug(b);</span><br><span class="line">    debug(c);</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-5-Gathering-Writes"><a href="#2-5-Gathering-Writes" class="headerlink" title="2.5 Gathering Writes"></a>2.5 Gathering Writes</h3><p>使用如下方式写入，可以将多个 buffer 的数据填充至 channel</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> (<span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="string">&quot;helloword/3parts.txt&quot;</span>, <span class="string">&quot;rw&quot;</span>)) &#123;</span><br><span class="line">    <span class="type">FileChannel</span> <span class="variable">channel</span> <span class="operator">=</span> file.getChannel();</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">d</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">4</span>);</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">e</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">4</span>);</span><br><span class="line">    channel.position(<span class="number">11</span>);</span><br><span class="line">    d.put(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="string">&#x27;f&#x27;</span>, <span class="string">&#x27;o&#x27;</span>, <span class="string">&#x27;u&#x27;</span>, <span class="string">&#x27;r&#x27;</span>&#125;);</span><br><span class="line">    e.put(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="string">&#x27;f&#x27;</span>, <span class="string">&#x27;i&#x27;</span>, <span class="string">&#x27;v&#x27;</span>, <span class="string">&#x27;e&#x27;</span>&#125;);</span><br><span class="line">    d.flip();</span><br><span class="line">    e.flip();</span><br><span class="line">    debug(d);</span><br><span class="line">    debug(e);</span><br><span class="line">    channel.write(<span class="keyword">new</span> <span class="title class_">ByteBuffer</span>[]&#123;d, e&#125;);</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-6-黏包，半包"><a href="#2-6-黏包，半包" class="headerlink" title="2.6 黏包，半包"></a>2.6 黏包，半包</h3><p>网络上有多条数据发送给服务端，数据之间使用 \n 进行分隔<br>但由于某种原因这些数据在接收时，被进行了重新组合，例如原始数据有3条为</p>
<ul>
<li>Hello,world\n</li>
<li>I’m zhangsan\n</li>
<li>How are you?\n</li>
</ul>
<p>变成了下面的两个 byteBuffer (黏包，半包)</p>
<ul>
<li>Hello,world\nI’m zhangsan\nHo</li>
<li>w are you?\n</li>
</ul>
<p>现在要求你编写程序，将错乱的数据恢复成原始的按 \n 分隔的数据</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">source</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">32</span>);</span><br><span class="line">    source.put(<span class="string">&quot;Hello,world\nI&#x27;m zhangsan\nHo&quot;</span>.getBytes());</span><br><span class="line">    split(source);</span><br><span class="line">    source.put(<span class="string">&quot;w are you?\nhaha!\n&quot;</span>.getBytes());</span><br><span class="line">    split(source);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">split</span><span class="params">(ByteBuffer source)</span> &#123;</span><br><span class="line">    source.flip();</span><br><span class="line">    <span class="type">int</span> <span class="variable">oldLimit</span> <span class="operator">=</span> source.limit();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; oldLimit; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (source.get(i) == <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">target</span> <span class="operator">=</span> ByteBuffer.allocate(i + <span class="number">1</span> - source.position());</span><br><span class="line">            <span class="comment">// 0 ~ limit</span></span><br><span class="line">            source.limit(i + <span class="number">1</span>);</span><br><span class="line">            target.put(source); <span class="comment">// 从source 读，向 target 写</span></span><br><span class="line">            debugAll(target);<span class="comment">//输出打印</span></span><br><span class="line">            source.limit(oldLimit);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    source.compact();<span class="comment">//注意是compact()，把未读完的部分向前压缩，然后切换至写模式</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="3-文件编程"><a href="#3-文件编程" class="headerlink" title="3. 文件编程"></a>3. 文件编程</h2><h3 id="3-1-FileChannel"><a href="#3-1-FileChannel" class="headerlink" title="3.1 FileChannel"></a>3.1 FileChannel</h3><h4 id="FileChannel-工作模式"><a href="#FileChannel-工作模式" class="headerlink" title="FileChannel 工作模式"></a>FileChannel 工作模式</h4><blockquote>
<p>FileChannel 只能工作在阻塞模式下</p>
</blockquote>
<h4 id="获取"><a href="#获取" class="headerlink" title="获取"></a>获取</h4><p>不能直接打开 FileChannel，必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel，它们都有 getChannel 方法</p>
<ul>
<li>通过 FileInputStream 获取的 channel 只能读</li>
<li>通过 FileOutputStream 获取的 channel 只能写</li>
<li>通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定</li>
</ul>
<h4 id="读取"><a href="#读取" class="headerlink" title="读取"></a>读取</h4><p>会从 channel 读取数据填充 ByteBuffer，返回值表示读到了多少字节，-1 表示到达了文件的末尾</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">readBytes</span> <span class="operator">=</span> channel.read(buffer);</span><br></pre></td></tr></table></figure>

<h4 id="写入"><a href="#写入" class="headerlink" title="写入"></a>写入</h4><p>写入的正确姿势如下， SocketChannel</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ...;</span><br><span class="line">buffer.put(...); <span class="comment">// 存入数据</span></span><br><span class="line">buffer.flip();   <span class="comment">// 切换读模式</span></span><br><span class="line"><span class="keyword">while</span>(buffer.hasRemaining()) &#123;</span><br><span class="line">    channel.write(buffer);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel</p>
<h4 id="关闭"><a href="#关闭" class="headerlink" title="关闭"></a>关闭</h4><p>channel 必须关闭，不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法</p>
<h4 id="位置"><a href="#位置" class="headerlink" title="位置"></a>位置</h4><p>获取当前位置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">pos</span> <span class="operator">=</span> channel.position();</span><br></pre></td></tr></table></figure>

<p>设置当前位置</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">newPos</span> <span class="operator">=</span> ...;</span><br><span class="line">channel.position(newPos);</span><br></pre></td></tr></table></figure>

<p>设置当前位置时，如果设置为文件的末尾</p>
<ul>
<li>这时读取会返回 -1 </li>
<li>这时写入，会追加内容，但要注意如果 position 超过了文件末尾，再写入时在新内容和原末尾之间会有空洞（00）</li>
</ul>
<h4 id="大小"><a href="#大小" class="headerlink" title="大小"></a>大小</h4><p>使用 size 方法获取文件的大小</p>
<h4 id="强制写入"><a href="#强制写入" class="headerlink" title="强制写入"></a>强制写入</h4><p>操作系统出于性能的考虑，会将数据缓存，不是立刻写入磁盘。可以调用 force(true)  方法将文件内容和元数据（文件的权限等信息）立刻写入磁盘</p>
<h3 id="3-2-两个-Channel-传输数据"><a href="#3-2-两个-Channel-传输数据" class="headerlink" title="3.2 两个 Channel 传输数据"></a>3.2 两个 Channel 传输数据</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">FROM</span> <span class="operator">=</span> <span class="string">&quot;helloword/data.txt&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">TO</span> <span class="operator">=</span> <span class="string">&quot;helloword/to.txt&quot;</span>;</span><br><span class="line"><span class="keyword">try</span> (<span class="type">FileChannel</span> <span class="variable">from</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(FROM).getChannel();</span><br><span class="line">     <span class="type">FileChannel</span> <span class="variable">to</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(TO).getChannel();</span><br><span class="line">    ) &#123;</span><br><span class="line">    from.transferTo(<span class="number">0</span>, from.size(), to);<span class="comment">//位置，大小，to对象</span></span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>相比buffer读写效率更高，底层会利用操作系统的零拷贝进行优化</p>
<p>大小有限制，不能超过2g</p>
</blockquote>
<p>超过 2g 大小的文件传输</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> (</span><br><span class="line">    <span class="type">FileChannel</span> <span class="variable">from</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="string">&quot;data.txt&quot;</span>).getChannel();</span><br><span class="line">    <span class="type">FileChannel</span> <span class="variable">to</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(<span class="string">&quot;to.txt&quot;</span>).getChannel();</span><br><span class="line">) &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">size</span> <span class="operator">=</span> from.size();</span><br><span class="line">    <span class="comment">// left 变量代表还剩余多少字节</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">long</span> <span class="variable">left</span> <span class="operator">=</span> from.size();; left &gt; <span class="number">0</span>; ) &#123;</span><br><span class="line">        left -= from.transferTo((size - left), left, to);</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-3-Path"><a href="#3-3-Path" class="headerlink" title="3.3 Path"></a>3.3 Path</h3><p>jdk7 引入了 Path 和 Paths 类</p>
<ul>
<li>Path 用来表示文件路径</li>
<li>Paths 是工具类，用来获取 Path 实例</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;1.txt&quot;</span>); <span class="comment">// 相对路径 使用 user.dir 环境变量来定位 1.txt</span></span><br><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\1.txt&quot;</span>); <span class="comment">// 绝对路径 代表了  d:\1.txt</span></span><br><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:/1.txt&quot;</span>); <span class="comment">// 绝对路径 同样代表了  d:\1.txt</span></span><br><span class="line"><span class="type">Path</span> <span class="variable">projects</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\data&quot;</span>, <span class="string">&quot;projects&quot;</span>); <span class="comment">// 代表了  d:\data\projects</span></span><br></pre></td></tr></table></figure>

<ul>
<li><code>.</code> 代表了当前路径</li>
<li><code>..</code> 代表了上一级路径</li>
</ul>
<p>例如目录结构如下</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">d:</span><br><span class="line">	|- data</span><br><span class="line">		|- projects</span><br><span class="line">			|- a</span><br><span class="line">			|- b</span><br></pre></td></tr></table></figure>

<p>代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\data\\projects\\a\\..\\b&quot;</span>);</span><br><span class="line">System.out.println(path);</span><br><span class="line">System.out.println(path.normalize()); <span class="comment">// 正常化路径</span></span><br></pre></td></tr></table></figure>

<p>会输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">d:\data\projects\a\..\b</span><br><span class="line">d:\data\projects\b</span><br></pre></td></tr></table></figure>

<h3 id="3-4-Files"><a href="#3-4-Files" class="headerlink" title="3.4 Files"></a>3.4 Files</h3><p>检查文件是否存在</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line">System.out.println(Files.exists(path));</span><br></pre></td></tr></table></figure>

<p>创建一级目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/d1&quot;</span>);</span><br><span class="line">Files.createDirectory(path);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果目录已存在，会抛异常 FileAlreadyExistsException</li>
<li>不能一次创建多级目录，否则会抛异常 NoSuchFileException</li>
</ul>
<p>创建多级目录用</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/d1/d2&quot;</span>);</span><br><span class="line">Files.createDirectories(path);</span><br></pre></td></tr></table></figure>

<p>拷贝文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/target.txt&quot;</span>);</span><br><span class="line">Files.copy(source, target);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果文件已存在，会抛异常 FileAlreadyExistsException</li>
</ul>
<p>如果希望用 source 覆盖掉 target，需要用 StandardCopyOption 来控制</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);</span><br></pre></td></tr></table></figure>

<p>移动文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">source</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line">Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);</span><br></pre></td></tr></table></figure>

<ul>
<li>StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性</li>
</ul>
<p>删除文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/target.txt&quot;</span>);</span><br><span class="line">Files.delete(target);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果文件不存在，会抛异常 NoSuchFileException</li>
</ul>
<p>删除目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">target</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;helloword/d1&quot;</span>);</span><br><span class="line">Files.delete(target);</span><br></pre></td></tr></table></figure>

<ul>
<li>如果目录还有内容，会抛异常 DirectoryNotEmptyException</li>
</ul>
<p>遍历目录文件**(访问者模式)**</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;C:\\Program Files\\Java\\jdk1.8.0_91&quot;</span>);</span><br><span class="line">    <span class="type">AtomicInteger</span> <span class="variable">dirCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">    <span class="type">AtomicInteger</span> <span class="variable">fileCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">    Files.walkFileTree(path, <span class="keyword">new</span> <span class="title class_">SimpleFileVisitor</span>&lt;Path&gt;()&#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> FileVisitResult <span class="title function_">preVisitDirectory</span><span class="params">(Path dir, BasicFileAttributes attrs)</span> </span><br><span class="line">            <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            System.out.println(dir);</span><br><span class="line">            dirCount.incrementAndGet();</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.preVisitDirectory(dir, attrs);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> FileVisitResult <span class="title function_">visitFile</span><span class="params">(Path file, BasicFileAttributes attrs)</span> </span><br><span class="line">            <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            System.out.println(file);</span><br><span class="line">            fileCount.incrementAndGet();</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">super</span>.visitFile(file, attrs);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    System.out.println(dirCount); <span class="comment">// 133</span></span><br><span class="line">    System.out.println(fileCount); <span class="comment">// 1479</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>比如统计 jar 的数目</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;C:\\Program Files\\Java\\jdk1.8.0_91&quot;</span>);</span><br><span class="line"><span class="type">AtomicInteger</span> <span class="variable">fileCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">Files.walkFileTree(path, <span class="keyword">new</span> <span class="title class_">SimpleFileVisitor</span>&lt;Path&gt;()&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> FileVisitResult <span class="title function_">visitFile</span><span class="params">(Path file, BasicFileAttributes attrs)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">if</span> (file.toFile().getName().endsWith(<span class="string">&quot;.jar&quot;</span>)) &#123;</span><br><span class="line">            fileCount.incrementAndGet();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.visitFile(file, attrs);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">System.out.println(fileCount); </span><br></pre></td></tr></table></figure>

<p>删除多级目录(从里到外删除)</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(<span class="string">&quot;d:\\a&quot;</span>);</span><br><span class="line">Files.walkFileTree(path, <span class="keyword">new</span> <span class="title class_">SimpleFileVisitor</span>&lt;Path&gt;()&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> FileVisitResult <span class="title function_">visitFile</span><span class="params">(Path file, BasicFileAttributes attrs)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        Files.delete(file);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.visitFile(file, attrs);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> FileVisitResult <span class="title function_">postVisitDirectory</span><span class="params">(Path dir, IOException exc)</span> </span><br><span class="line">        <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        Files.delete(dir);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.postVisitDirectory(dir, exc);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<blockquote>
<p>删除是危险操作，没有回收站，确保要递归删除的文件夹没有重要内容</p>
</blockquote>
<p>拷贝多级目录</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">source</span> <span class="operator">=</span> <span class="string">&quot;D:\\Snipaste-1.16.2-x64&quot;</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">target</span> <span class="operator">=</span> <span class="string">&quot;D:\\Snipaste-1.16.2-x64aaa&quot;</span>;</span><br><span class="line"></span><br><span class="line">Files.walk(Paths.get(source)).forEach(path -&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">targetName</span> <span class="operator">=</span> path.toString().replace(source, target);</span><br><span class="line">        <span class="comment">// 是目录</span></span><br><span class="line">        <span class="keyword">if</span> (Files.isDirectory(path)) &#123;</span><br><span class="line">            Files.createDirectory(Paths.get(targetName));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 是普通文件</span></span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (Files.isRegularFile(path)) &#123;</span><br><span class="line">            Files.copy(path, Paths.get(targetName));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="4-网络编程"><a href="#4-网络编程" class="headerlink" title="4. 网络编程"></a>4. 网络编程</h2><h3 id="4-1-非阻塞-vs-阻塞"><a href="#4-1-非阻塞-vs-阻塞" class="headerlink" title="4.1 非阻塞 vs 阻塞"></a>4.1 非阻塞 vs 阻塞</h3><h4 id="阻塞"><a href="#阻塞" class="headerlink" title="阻塞"></a>阻塞</h4><ul>
<li>阻塞模式下，相关方法都会导致线程暂停<ul>
<li>ServerSocketChannel.accept 会在没有连接建立时让线程暂停</li>
<li>SocketChannel.read 会在没有数据可读时让线程暂停</li>
<li>阻塞的表现其实就是线程暂停了，暂停期间不会占用 cpu，但线程相当于闲置</li>
</ul>
</li>
<li>单线程下，阻塞方法之间相互影响，几乎不能正常工作，需要多线程支持</li>
<li>但多线程下，有新的问题，体现在以下方面<ul>
<li>32 位 jvm 一个线程 320k，64 位 jvm 一个线程 1024k，如果连接数过多，必然导致 OOM，并且线程太多，反而会因为频繁上下文切换导致性能降低</li>
<li>可以采用线程池技术来减少线程数和线程上下文切换，但治标不治本，如果有很多连接建立，但长时间 inactive，会阻塞线程池中所有线程，因此不适合长连接，只适合短连接</li>
</ul>
</li>
</ul>
<p>服务器端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 nio 来理解阻塞模式, 单线程</span></span><br><span class="line"><span class="comment">// 0. ByteBuffer</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line"><span class="comment">// 1. 创建了服务器</span></span><br><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line"><span class="comment">// 2. 绑定监听端口</span></span><br><span class="line">ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line"><span class="comment">// 3. 连接集合</span></span><br><span class="line">List&lt;SocketChannel&gt; channels = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="comment">// 4. accept 建立与客户端连接， SocketChannel 用来与客户端之间通信</span></span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept(); <span class="comment">// 阻塞方法，没收到连接线程就停止运行了</span></span><br><span class="line">    channels.add(sc);</span><br><span class="line">    <span class="keyword">for</span> (SocketChannel channel : channels) &#123;</span><br><span class="line">        <span class="comment">// 5. 接收客户端发送的数据</span></span><br><span class="line">        channel.read(buffer); <span class="comment">// 阻塞方法，没数据线程也会停止运行</span></span><br><span class="line">        buffer.flip();</span><br><span class="line">        debugRead(buffer);</span><br><span class="line">        buffer.clear();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">sc.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br></pre></td></tr></table></figure>

<h4 id="非阻塞"><a href="#非阻塞" class="headerlink" title="非阻塞"></a>非阻塞</h4><ul>
<li>非阻塞模式下，相关方法都会不会让线程暂停<ul>
<li>在 ServerSocketChannel.accept 在没有连接建立时，会返回 null，继续运行</li>
<li>SocketChannel.read 在没有数据可读时，会返回 0，但线程不必阻塞，可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept </li>
<li>写数据时，线程只是等待数据写入 Channel 即可，无需等 Channel 通过网络把数据发送出去</li>
</ul>
</li>
<li>但非阻塞模式下，即使没有连接建立，和可读数据，线程仍然在不断运行，白白浪费了 cpu</li>
<li>数据复制过程中，线程实际还是阻塞的（AIO 改进的地方）</li>
</ul>
<p>服务器端，客户端代码不变</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line"><span class="comment">// 1. 创建了服务器</span></span><br><span class="line"><span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">ssc.configureBlocking(<span class="literal">false</span>); <span class="comment">// 非阻塞模式</span></span><br><span class="line"><span class="comment">// 2. 绑定监听端口</span></span><br><span class="line">ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line"><span class="comment">// 3. 连接集合</span></span><br><span class="line">List&lt;SocketChannel&gt; channels = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"><span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept(); <span class="comment">// 非阻塞，线程还会继续运行，如果没有连接建立，但sc是null</span></span><br><span class="line">    <span class="keyword">if</span> (sc != <span class="literal">null</span>) &#123;</span><br><span class="line">        log.debug(<span class="string">&quot;connected... &#123;&#125;&quot;</span>, sc);</span><br><span class="line">        sc.configureBlocking(<span class="literal">false</span>); <span class="comment">// 非阻塞模式</span></span><br><span class="line">        channels.add(sc);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (SocketChannel channel : channels) &#123;</span><br><span class="line">        <span class="comment">// 5. 接收客户端发送的数据</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> channel.read(buffer);<span class="comment">// 非阻塞，线程仍然会继续运行，如果没有读到数据，read 返回 0</span></span><br><span class="line">        <span class="keyword">if</span> (read &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            buffer.flip();</span><br><span class="line">            debugRead(buffer);</span><br><span class="line">            buffer.clear();</span><br><span class="line">            log.debug(<span class="string">&quot;after read...&#123;&#125;&quot;</span>, channel);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="多路复用"><a href="#多路复用" class="headerlink" title="多路复用"></a>多路复用</h4><p>单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控，这称之为多路复用</p>
<ul>
<li>多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用</li>
<li>如果不用 Selector 的非阻塞模式，线程大部分时间都在做无用功，而 Selector 能够保证<ul>
<li>有可连接事件时才去连接</li>
<li>有可读事件才去读取</li>
<li>有可写事件才去写入<ul>
<li>限于网络传输能力，Channel 未必时时可写，一旦 Channel 可写，会触发 Selector 的可写事件</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="4-2-Selector"><a href="#4-2-Selector" class="headerlink" title="4.2 Selector"></a>4.2 Selector</h3><p>好处</p>
<ul>
<li>一个线程配合 selector 就可以监控多个 channel 的事件，事件发生线程才去处理。避免非阻塞模式下所做无用功</li>
<li>让这个线程能够被充分利用</li>
<li>节约了线程的数量</li>
<li>减少了线程上下文切换</li>
</ul>
<h4 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br></pre></td></tr></table></figure>

<h4 id="绑定-Channel-事件"><a href="#绑定-Channel-事件" class="headerlink" title="绑定 Channel 事件"></a>绑定 Channel 事件</h4><p>也称之为注册事件，绑定的事件 selector 才会关心 </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line"><span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> channel.register(selector, 绑定事件);</span><br></pre></td></tr></table></figure>

<ul>
<li>channel 必须工作在非阻塞模式</li>
<li>FileChannel 没有非阻塞模式，因此不能配合 selector 一起使用</li>
<li>绑定的事件类型可以有<ul>
<li>connect - 针对客户端，连接成功时触发</li>
<li>accept - 连接请求时时触发，SelectionKey.OP_ACCEPT</li>
<li>read - 数据可读入时触发，有因为接收能力弱，数据暂不能读入的情况</li>
<li>write - 数据可写出时触发，有因为发送能力弱，数据暂不能写出的情况</li>
</ul>
</li>
</ul>
<h4 id="监听-Channel-事件"><a href="#监听-Channel-事件" class="headerlink" title="监听 Channel 事件"></a>监听 Channel 事件</h4><p>可以通过下面三种方法来监听是否有事件发生，方法的返回值代表有多少 channel 发生了事件</p>
<p>方法1，阻塞直到绑定事件发生</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br></pre></td></tr></table></figure>

<p>方法2，阻塞直到绑定事件发生，或是超时（时间单位为 ms）</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select(<span class="type">long</span> timeout);</span><br></pre></td></tr></table></figure>

<p>方法3，不会阻塞，也就是不管有没有事件，立刻返回，自己根据返回值检查是否有事件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.selectNow();</span><br></pre></td></tr></table></figure>

<h4 id="select-何时不阻塞"><a href="#select-何时不阻塞" class="headerlink" title="select 何时不阻塞"></a>select 何时不阻塞</h4><blockquote>
<ul>
<li>事件发生时<ul>
<li>客户端发起连接请求，会触发 accept 事件</li>
<li>客户端发送数据过来，客户端正常、异常关闭时，都会触发 read 事件，另外如果发送的数据大于 buffer 缓冲区，会触发多次读取事件</li>
<li>channel 可写，会触发 write 事件</li>
<li>在 linux 下 nio bug 发生时</li>
</ul>
</li>
<li>调用 selector.wakeup()</li>
<li>调用 selector.close()</li>
<li>selector 所在线程 interrupt</li>
</ul>
</blockquote>
<h3 id="4-3-处理-accept-事件"><a href="#4-3-处理-accept-事件" class="headerlink" title="4.3 处理 accept 事件"></a>4.3 处理 accept 事件</h3><p>客户端代码为</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Client</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Socket</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>)) &#123;</span><br><span class="line">            socket.getOutputStream().write(<span class="string">&quot;world&quot;</span>.getBytes());</span><br><span class="line">            System.in.read();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>服务器端代码为</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo6</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ServerSocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> ServerSocketChannel.open()) &#123;</span><br><span class="line">            channel.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">            channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">            <span class="comment">//1.创建selector，管理多个channel</span></span><br><span class="line">            <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">            <span class="comment">//2.注册事件，建立selector和channel之间的联系</span></span><br><span class="line">            channel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="comment">//3.select()方法，没有事件发生，线程阻塞，有事件线程恢复运行</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br><span class="line">                <span class="comment">// 4. 获取所有事件</span></span><br><span class="line">                Set&lt;SelectionKey&gt; keys = selector.selectedKeys();</span><br><span class="line">                <span class="comment">// 5.遍历所有事件，逐一处理</span></span><br><span class="line">                Iterator&lt;SelectionKey&gt; iter = keys.iterator();</span><br><span class="line">                <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                    <span class="comment">// 判断事件类型</span></span><br><span class="line">                    <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                        <span class="type">ServerSocketChannel</span> <span class="variable">c</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                        <span class="comment">// 必须处理</span></span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> c.accept();</span><br><span class="line">                        log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, sc);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 处理完毕，必须将事件移除</span></span><br><span class="line">                    iter.remove();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>事件发生后能否不处理</strong></p>
<blockquote>
<p>事件发生后，要么处理，要么取消（cancel），不能什么都不做，否则下次该事件仍会触发，这是因为 nio 底层使用的是水平触发</p>
</blockquote>
<h3 id="4-4-处理-read-事件"><a href="#4-4-处理-read-事件" class="headerlink" title="4.4 处理 read 事件"></a>4.4 处理 read 事件</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChannelDemo6</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ServerSocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> ServerSocketChannel.open()) &#123;</span><br><span class="line">            channel.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">            System.out.println(channel);</span><br><span class="line">            <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">            channel.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">            channel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> selector.select();</span><br><span class="line">                Set&lt;SelectionKey&gt; keys = selector.selectedKeys();</span><br><span class="line">                Iterator&lt;SelectionKey&gt; iter = keys.iterator();</span><br><span class="line">                <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                    <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                        <span class="type">ServerSocketChannel</span> <span class="variable">c</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> c.accept();</span><br><span class="line">                        sc.configureBlocking(<span class="literal">false</span>);<span class="comment">//非阻塞</span></span><br><span class="line">                        sc.register(selector, SelectionKey.OP_READ);<span class="comment">//注册</span></span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123;</span><br><span class="line">                        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">128</span>);</span><br><span class="line">                        <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> sc.read(buffer);</span><br><span class="line">                        <span class="keyword">if</span>(read == -<span class="number">1</span>) &#123;</span><br><span class="line">                            key.cancel();</span><br><span class="line">                            sc.close();</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            buffer.flip();</span><br><span class="line">                          System.out.println(Charaset.defaultCharaset().decode(buffer));</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 处理完毕，必须将事件移除，不然下一次循环 SocketChannel sc = c.accept()得到的是null</span></span><br><span class="line">                    iter.remove();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>为何要 iter.remove()</strong></p>
<p>select 在事件发生后，就会将相关的 key 放入 selectedKeys 集合，但不会在处理完后从 selectedKeys 集合中移除，需要自行编码删除。例如</p>
<ul>
<li>第一次触发了 ssckey 上的 accept 事件，没有移除 ssckey </li>
<li>第二次触发了 sckey 上的 read 事件，SocketChannel sc = c.accept()得到的是null，就会导致空指针异常</li>
</ul>
<p><strong>cancel 的作用</strong></p>
<p>cancel 会取消注册在 selector 上的 channel，并从 keys 集合中删除 key 后续不会再监听事件</p>
<blockquote>
<p>当客户端断开后，同样会产生一次read事件需要将key.cancel()将key取消，</p>
</blockquote>
<h4 id="处理消息的边界"><a href="#处理消息的边界" class="headerlink" title="处理消息的边界"></a>处理消息的边界</h4><p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422142959581.png" alt="image-20220422142959581"></p>
<ul>
<li>一种思路是固定消息长度，数据包大小一样，服务器按预定长度读取，缺点是浪费带宽</li>
<li>另一种思路是按分隔符拆分，缺点是效率低</li>
<li>TLV 格式，即 Type 类型、Length 长度、Value 数据，类型和长度已知的情况下，就可以方便获取消息大小，分配合适的 buffer，缺点是 buffer 需要提前分配，如果内容过大，则影响 server 吞吐量<ul>
<li>Http 1.1 是 TLV 格式</li>
<li>Http 2.0 是 LTV 格式(长度、类型、内容)</li>
</ul>
</li>
</ul>
<p>先以第二种方式为例</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">sequenceDiagram </span><br><span class="line">participant c1 as 客户端1</span><br><span class="line">participant s as 服务器</span><br><span class="line">participant b1 as ByteBuffer1</span><br><span class="line">participant b2 as ByteBuffer2</span><br><span class="line">c1 -&gt;&gt; s: 发送 01234567890abcdef3333\r</span><br><span class="line">s -&gt;&gt; b1: 第一次 read 存入 01234567890abcdef</span><br><span class="line">s -&gt;&gt; b2: 扩容</span><br><span class="line">b1 -&gt;&gt; b2: 拷贝 01234567890abcdef</span><br><span class="line">s -&gt;&gt; b2: 第二次 read 存入 3333\r</span><br><span class="line">b2 -&gt;&gt; b2: 01234567890abcdef3333\r</span><br></pre></td></tr></table></figure>

<p>服务器端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">split</span><span class="params">(ByteBuffer source)</span> &#123;</span><br><span class="line">    source.flip();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; source.limit(); i++) &#123;</span><br><span class="line">        <span class="comment">// 找到一条完整消息</span></span><br><span class="line">        <span class="keyword">if</span> (source.get(i) == <span class="string">&#x27;\n&#x27;</span>) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> i + <span class="number">1</span> - source.position();</span><br><span class="line">            <span class="comment">// 把这条完整消息存入新的 ByteBuffer</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">target</span> <span class="operator">=</span> ByteBuffer.allocate(length);</span><br><span class="line">            <span class="comment">// 从 source 读，向 target 写</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; length; j++) &#123;</span><br><span class="line">                target.put(source.get());</span><br><span class="line">            &#125;</span><br><span class="line">            debugAll(target);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    source.compact(); </span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">    <span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">    ssc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">    <span class="type">SelectionKey</span> <span class="variable">sscKey</span> <span class="operator">=</span> ssc.register(selector, <span class="number">0</span>, <span class="literal">null</span>);</span><br><span class="line">    sscKey.interestOps(SelectionKey.OP_ACCEPT);</span><br><span class="line">    ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        selector.select();</span><br><span class="line">        Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator(); </span><br><span class="line">        <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">            <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">            iter.remove();</span><br><span class="line">            <span class="keyword">if</span> (key.isAcceptable()) &#123; </span><br><span class="line">                <span class="type">ServerSocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> (ServerSocketChannel) key.channel();</span><br><span class="line">                <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> channel.accept();</span><br><span class="line">                sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>); <span class="comment">// attachment</span></span><br><span class="line">                <span class="comment">// 将一个 byteBuffer 作为附件关联到 selectionKey 上</span></span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">scKey</span> <span class="operator">=</span> sc.register(selector, <span class="number">0</span>, buffer);</span><br><span class="line">                scKey.interestOps(SelectionKey.OP_READ);</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isReadable()) &#123; </span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> (SocketChannel) key.channel(); <span class="comment">// 拿到触发事件的channel</span></span><br><span class="line">                    <span class="comment">// 获取 selectionKey 上关联的附件</span></span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> (ByteBuffer) key.attachment();</span><br><span class="line">                    <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> channel.read(buffer); </span><br><span class="line">                    <span class="keyword">if</span>(read == -<span class="number">1</span>) &#123;</span><br><span class="line">                        key.cancel();</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        split(buffer);</span><br><span class="line">                        <span class="comment">// compact()会移动position</span></span><br><span class="line">                        <span class="comment">// 需要扩容</span></span><br><span class="line">                        <span class="keyword">if</span> (buffer.position() == buffer.limit()) &#123;</span><br><span class="line">                            <span class="type">ByteBuffer</span> <span class="variable">newBuffer</span> <span class="operator">=</span> ByteBuffer.allocate(buffer.capacity() * <span class="number">2</span>);</span><br><span class="line">                            buffer.flip();</span><br><span class="line">                            newBuffer.put(buffer); </span><br><span class="line">                            key.attach(newBuffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                    key.cancel(); </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>需要保证每个SocketChannel拥有一个自己的buffer，这样即使触发两次read事件也是同一个buffer,通过附件实现</p>
</blockquote>
<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">sc.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br><span class="line"><span class="type">SocketAddress</span> <span class="variable">address</span> <span class="operator">=</span> sc.getLocalAddress();</span><br><span class="line">sc.write(Charset.defaultCharset().encode(<span class="string">&quot;0123456789abcdef3333\n&quot;</span>));</span><br><span class="line">System.in.read();<span class="comment">//只是让客户端阻塞住</span></span><br></pre></td></tr></table></figure>

<h4 id="ByteBuffer-大小分配"><a href="#ByteBuffer-大小分配" class="headerlink" title="ByteBuffer 大小分配"></a>ByteBuffer 大小分配</h4><ul>
<li>每个 channel 都需要记录可能被切分的消息，因为 ByteBuffer 不能被多个 channel 共同使用，因此需要为每个 channel 维护一个独立的 ByteBuffer</li>
<li>ByteBuffer 不能太大，比如一个 ByteBuffer 1Mb 的话，要支持百万连接就要 1Tb 内存，因此需要设计大小可变的 ByteBuffer<ul>
<li>一种思路是首先分配一个较小的 buffer，例如 4k，如果发现数据不够，再分配 8k 的 buffer，将 4k buffer 内容拷贝至 8k buffer，优点是消息连续容易处理，缺点是数据拷贝耗费性能，参考实现 <a target="_blank" rel="noopener" href="http://tutorials.jenkov.com/java-performance/resizable-array.html">http://tutorials.jenkov.com/java-performance/resizable-array.html</a></li>
<li>另一种思路是用多个数组组成 buffer，一个数组不够，把多出来的内容写入新的数组，与前面的区别是消息存储不连续解析复杂，优点是避免了拷贝引起的性能损耗</li>
</ul>
</li>
</ul>
<h3 id="4-5-处理-write-事件"><a href="#4-5-处理-write-事件" class="headerlink" title="4.5 处理 write 事件"></a>4.5 处理 write 事件</h3><ul>
<li>非阻塞模式下，无法保证把 buffer 中所有数据都写入 channel，因此需要追踪 write 方法的返回值（代表实际写入字节数）</li>
<li>用 selector 监听所有 channel 的可写事件，每个 channel 都需要一个 key 来跟踪 buffer，但这样又会导致占用内存过多，就有两阶段策略<ul>
<li>当消息处理器第一次写入消息时，才将 channel 注册到 selector 上</li>
<li>selector 检查 channel 上的可写事件，如果所有的数据写完了，就取消 channel 的注册</li>
<li>如果不取消，会每次可写均会触发 write 事件</li>
</ul>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WriteServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">        ssc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">        ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">        <span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">        ssc.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">            selector.select();</span><br><span class="line">            Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator();</span><br><span class="line">            <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                iter.remove();<span class="comment">//赶紧移除</span></span><br><span class="line">                <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept();</span><br><span class="line">                    sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                    <span class="type">SelectionKey</span> <span class="variable">sckey</span> <span class="operator">=</span> sc.register(selector, SelectionKey.OP_READ);</span><br><span class="line">                    <span class="comment">// 1. 模拟向客户端发送大量数据</span></span><br><span class="line">                    <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">                    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">3000000</span>; i++) &#123;</span><br><span class="line">                        sb.append(<span class="string">&quot;a&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> Charset.defaultCharset().encode(sb.toString());</span><br><span class="line">                    <span class="comment">// 3. write 表示实际写了多少字节</span></span><br><span class="line">                    <span class="type">int</span> <span class="variable">write</span> <span class="operator">=</span> sc.write(buffer);</span><br><span class="line">                    System.out.println(<span class="string">&quot;实际写入字节:&quot;</span> + write);</span><br><span class="line">                    <span class="comment">// 4. 如果有剩余未读字节，才需要关注写事件</span></span><br><span class="line">                    <span class="keyword">if</span> (buffer.hasRemaining()) &#123;</span><br><span class="line">                        <span class="comment">// 在原有关注事件的基础上，多关注写事件</span></span><br><span class="line">                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);</span><br><span class="line">                        <span class="comment">// 5.把 buffer 作为附件加入 sckey</span></span><br><span class="line">                        sckey.attach(buffer);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (key.isWritable()) &#123;</span><br><span class="line">                    <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> (ByteBuffer) key.attachment();</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                    <span class="type">int</span> <span class="variable">write</span> <span class="operator">=</span> sc.write(buffer);</span><br><span class="line">                    System.out.println(<span class="string">&quot;实际写入字节:&quot;</span> + write);</span><br><span class="line">                    <span class="keyword">if</span> (!buffer.hasRemaining()) &#123; <span class="comment">// 写完了</span></span><br><span class="line">                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);</span><br><span class="line">                        key.attach(<span class="literal">null</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>write 为何要取消？</p>
<p>只要向 channel 发送数据时，socket 缓冲可写，这个事件会频繁触发，因此应当只在 socket 缓冲区写不下时再关注可写事件，数据写完之后再取消关注</p>
</blockquote>
<p>客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WriteClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line">        sc.connect(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>));</span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line">            count += sc.read(buffer);</span><br><span class="line">            buffer.clear();</span><br><span class="line">            System.out.println(count);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="4-6-更进一步"><a href="#4-6-更进一步" class="headerlink" title="4.6 更进一步"></a>4.6 更进一步</h3><h4 id="利用多线程优化"><a href="#利用多线程优化" class="headerlink" title="利用多线程优化"></a>利用多线程优化</h4><p>前面的代码只有一个选择器，没有充分利用多核 cpu，改进：</p>
<p>分两组选择器</p>
<ul>
<li>单线程配一个选择器，专门处理 accept 事件</li>
<li>创建 cpu 核心数的线程，每个线程配一个选择器，轮流处理 read 事件</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiThreadServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="comment">//主线程，专门处理 accept 事件</span></span><br><span class="line">        Thread.currentThread().setName(<span class="string">&quot;boss&quot;</span>);</span><br><span class="line">        <span class="type">ServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> ServerSocketChannel.open();</span><br><span class="line">        ssc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">        <span class="type">Selector</span> <span class="variable">boss</span> <span class="operator">=</span> Selector.open();</span><br><span class="line">        <span class="type">SelectionKey</span> <span class="variable">bossKey</span> <span class="operator">=</span> ssc.register(boss, <span class="number">0</span>, <span class="literal">null</span>);</span><br><span class="line">        bossKey.interestOps(SelectionKey.OP_ACCEPT);</span><br><span class="line">        ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 1. 创建固定数量的 worker 并初始化</span></span><br><span class="line">        Worker[] workers = <span class="keyword">new</span> <span class="title class_">Worker</span>[Runtime.getRuntime().availableProcessors()];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; workers.length; i++) &#123;</span><br><span class="line">            workers[i] = <span class="keyword">new</span> <span class="title class_">Worker</span>(<span class="string">&quot;worker-&quot;</span> + i);</span><br><span class="line">            </span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">AtomicInteger</span> <span class="variable">index</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">            boss.select();</span><br><span class="line">            Iterator&lt;SelectionKey&gt; iter = boss.selectedKeys().iterator();</span><br><span class="line">            <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                iter.remove();</span><br><span class="line">                <span class="keyword">if</span> (key.isAcceptable()) &#123;</span><br><span class="line">                    <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> ssc.accept();</span><br><span class="line">                    sc.configureBlocking(<span class="literal">false</span>);</span><br><span class="line">                    workers[index.getAndIncrement() % workers.length].register(sc); <span class="comment">// boss 调用 初始化 selector , 启动 worker-0</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">class</span> <span class="title class_">Worker</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> Thread thread;</span><br><span class="line">        <span class="keyword">private</span> Selector selector;</span><br><span class="line">        <span class="keyword">private</span> String name;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">start</span> <span class="operator">=</span> <span class="literal">false</span>; <span class="comment">// 标记是否初始化了</span></span><br><span class="line">        <span class="comment">//boss线程和worker线程之间传递数据，两个线程之间的信息传递可以用队列</span></span><br><span class="line">        <span class="keyword">private</span> ConcurrentLinkedQueue&lt;Runnable&gt; queue = <span class="keyword">new</span> <span class="title class_">ConcurrentLinkedQueue</span>&lt;&gt;();</span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">Worker</span><span class="params">(String name)</span> &#123;<span class="built_in">this</span>.name = name&#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 初始化线程，和 selector</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(SocketChannel sc)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">            <span class="keyword">if</span>(!start) &#123;</span><br><span class="line">                selector = Selector.open();</span><br><span class="line">                thread = <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="built_in">this</span>, name);<span class="comment">//worker本身当作任务对象</span></span><br><span class="line">                thread.start();</span><br><span class="line">                start = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//向队列添加任务，但是任务并没有立即执行，要在woker的run()里面执行</span></span><br><span class="line">            queue.add(()-&gt;&#123;</span><br><span class="line">                <span class="comment">// 每次建立连接都要执行</span></span><br><span class="line">                sc.register(selector, SelectionKey.OP_READ, <span class="literal">null</span>);</span><br><span class="line">            &#125;); </span><br><span class="line">           	selector.wakeup(); <span class="comment">// 唤醒 select 方法</span></span><br><span class="line">        &#125;</span><br><span class="line">         </span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    selector.select();<span class="comment">//需要主动唤醒selector</span></span><br><span class="line">                    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> queue.poll();</span><br><span class="line">                    <span class="keyword">if</span>(task != <span class="literal">null</span>)&#123;</span><br><span class="line">                        <span class="comment">//执行了sc.register();</span></span><br><span class="line">                        task.run();</span><br><span class="line">                    &#125;</span><br><span class="line">                    Iterator&lt;SelectionKey&gt; iter = selector.selectedKeys().iterator();</span><br><span class="line">                    <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">                        <span class="type">SelectionKey</span> <span class="variable">key</span> <span class="operator">=</span> iter.next();</span><br><span class="line">                        iter.remove();</span><br><span class="line">                        <span class="keyword">if</span> (key.isReadable()) &#123;<span class="comment">//逻辑简化了，暂时不考虑异常，粘包等细节</span></span><br><span class="line">                            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line">                            <span class="type">SocketChannel</span> <span class="variable">channel</span> <span class="operator">=</span> (SocketChannel) key.channel();</span><br><span class="line">                            channel.read(buffer);</span><br><span class="line">                            buffer.flip();</span><br><span class="line">                            debugAll(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;<span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如何拿到 cpu 个数</p>
<blockquote>
<ul>
<li>Runtime.getRuntime().availableProcessors() 如果工作在 docker 容器下，因为容器不是物理隔离的，会拿到物理 cpu 个数，而不是容器申请时的个数</li>
<li>这个问题直到 jdk 10 才修复，使用 jvm 参数 UseContainerSupport 配置， 默认开启</li>
</ul>
</blockquote>
<h3 id="4-7-UDP"><a href="#4-7-UDP" class="headerlink" title="4.7 UDP"></a>4.7 UDP</h3><ul>
<li>UDP 是无连接的，client 发送数据不会管 server 是否开启</li>
<li>server 这边的 receive 方法会将接收到的数据存入 byte buffer，但如果数据报文超过 buffer 大小，多出来的数据会被默默抛弃</li>
</ul>
<p>首先启动服务器端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UdpServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">DatagramChannel</span> <span class="variable">channel</span> <span class="operator">=</span> DatagramChannel.open()) &#123;</span><br><span class="line">            channel.socket().bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">9999</span>));</span><br><span class="line">            System.out.println(<span class="string">&quot;waiting...&quot;</span>);</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">32</span>);</span><br><span class="line">            channel.receive(buffer);</span><br><span class="line">            buffer.flip();</span><br><span class="line">            debug(buffer);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">waiting...</span><br></pre></td></tr></table></figure>

<p>运行客户端</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UdpClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">DatagramChannel</span> <span class="variable">channel</span> <span class="operator">=</span> DatagramChannel.open()) &#123;</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> StandardCharsets.UTF_8.encode(<span class="string">&quot;hello&quot;</span>);</span><br><span class="line">            <span class="type">InetSocketAddress</span> <span class="variable">address</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="string">&quot;localhost&quot;</span>, <span class="number">9999</span>);</span><br><span class="line">            channel.send(buffer, address);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="5-NIO-vs-BIO"><a href="#5-NIO-vs-BIO" class="headerlink" title="5. NIO vs BIO"></a>5. NIO vs BIO</h2><h3 id="5-1-stream-vs-channel"><a href="#5-1-stream-vs-channel" class="headerlink" title="5.1 stream vs channel"></a>5.1 stream vs channel</h3><ul>
<li>stream 不会自动缓冲数据，channel 会利用系统提供的发送缓冲区、接收缓冲区（更为底层）</li>
<li>stream 仅支持阻塞 API，channel 同时支持阻塞、非阻塞 API，网络 channel 可配合 selector 实现多路复用</li>
<li>二者均为全双工，即读写可以同时进行</li>
</ul>
<h3 id="5-2-IO-模型"><a href="#5-2-IO-模型" class="headerlink" title="5.2 IO 模型"></a>5.2 IO 模型</h3><p>同步阻塞、同步非阻塞、同步多路复用、异步阻塞（没有此情况）、异步非阻塞</p>
<ul>
<li>同步：线程自己去获取结果<strong>（一个线程）</strong></li>
<li>异步：线程自己不去获取结果，而是由其它线程送结果<strong>（至少两个线程）</strong></li>
</ul>
<p>当调用一次 channel.read 或 stream.read 后，会切换至操作系统内核态来完成真正数据读取，而读取又分为两个阶段，分别为：</p>
<ul>
<li>等待数据阶段</li>
<li>复制数据阶段</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422190953485.png" alt="image-20220422190953485"></p>
<ul>
<li><p>阻塞 IO</p>
<p>用户线程被阻塞</p>
</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191022496.png" alt="image-20220422191022496"></p>
<ul>
<li><p>非阻塞  IO</p>
<p>用户线程不停的while(true)循环，但是复制数据时还是被阻塞的</p>
</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191037701.png" alt="image-20220422191037701"></p>
<ul>
<li><p>多路复用</p>
<p>阻塞住直到有事件发生，复制数据时也阻塞</p>
</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191057656.png" alt="image-20220422191057656"></p>
<ul>
<li>信号驱动</li>
<li>异步 IO</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191118224.png" alt="image-20220422191118224"></p>
<ul>
<li><p>阻塞 IO vs 多路复用</p>
<p>多路复用虽然也会阻塞，但能同时处理多个事件</p>
</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191137181.png" alt="image-20220422191137181"></p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422191147599.png" alt="image-20220422191147599"></p>
<p>参考</p>
<p>UNIX 网络编程 - 卷 I</p>
<h3 id="5-3-零拷贝"><a href="#5-3-零拷贝" class="headerlink" title="5.3 零拷贝"></a>5.3 零拷贝</h3><h4 id="传统-IO-问题"><a href="#传统-IO-问题" class="headerlink" title="传统 IO 问题"></a>传统 IO 问题</h4><p>传统的 IO 将一个文件通过 socket 写出</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">File</span> <span class="variable">f</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;helloword/data.txt&quot;</span>);</span><br><span class="line"><span class="type">RandomAccessFile</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(file, <span class="string">&quot;r&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">byte</span>[] buf = <span class="keyword">new</span> <span class="title class_">byte</span>[(<span class="type">int</span>)f.length()];</span><br><span class="line">file.read(buf);</span><br><span class="line"></span><br><span class="line"><span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> ...;</span><br><span class="line">socket.getOutputStream().write(buf);</span><br></pre></td></tr></table></figure>

<p>内部工作流程是这样的：</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422193433564.png" alt="image-20220422193433564"></p>
<ol>
<li><p>java 本身并不具备 IO 读写能力，因此 read 方法调用后，要从 java 程序的<strong>用户态</strong>切换至<strong>内核态</strong>，去调用操作系统（Kernel）的读能力，将数据读入<strong>内核缓冲区</strong>。这期间用户线程阻塞，操作系统使用 DMA（Direct Memory Access）来实现文件读，其间也不会使用 cpu</p>
<blockquote>
<p>DMA 也可以理解为硬件单元，用来解放 cpu 完成文件 IO</p>
</blockquote>
</li>
<li><p>从<strong>内核态</strong>切换回<strong>用户态</strong>，将数据从<strong>内核缓冲区</strong>读入<strong>用户缓冲区</strong>（即 byte[] buf），这期间 cpu 会参与拷贝，无法利用 DMA</p>
</li>
<li><p>调用 write 方法，这时将数据从<strong>用户缓冲区</strong>（byte[] buf）写入 <strong>socket 缓冲区</strong>，cpu 会参与拷贝</p>
</li>
<li><p>接下来要向网卡写数据，这项能力 java 又不具备，因此又得从<strong>用户态</strong>切换至<strong>内核态</strong>，调用操作系统的写能力，使用 DMA 将 <strong>socket 缓冲区</strong>的数据写入网卡，不会使用 cpu</p>
</li>
</ol>
<p>可以看到中间环节较多，java 的 IO 实际不是物理设备级别的读写，而是缓存的复制，底层的真正读写是操作系统来完成的</p>
<ul>
<li>用户态与内核态的切换发生了 3 次，这个操作比较重量级</li>
<li>数据拷贝了共 4 次</li>
</ul>
<h4 id="NIO-优化"><a href="#NIO-优化" class="headerlink" title="NIO 优化"></a>NIO 优化</h4><p>通过 DirectByteBuf </p>
<ul>
<li>ByteBuffer.allocate(10)  HeapByteBuffer 使用的还是 java 内存</li>
<li>ByteBuffer.allocateDirect(10)  DirectByteBuffer 使用的是操作系统内存</li>
</ul>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422193450515.png" alt="image-20220422193450515"></p>
<p>大部分步骤与优化前相同，不再赘述。唯有一点：java 可以使用 DirectByteBuf 将堆外内存映射到 jvm 内存中来直接访问使用</p>
<ul>
<li>这块内存不受 jvm 垃圾回收的影响，因此内存地址固定，有助于 IO 读写</li>
<li>java 中的 DirectByteBuf 对象仅维护了此内存的虚引用，内存回收分成两步<ul>
<li>DirectByteBuf 对象被垃圾回收，将虚引用加入引用队列</li>
<li>通过专门线程访问引用队列，根据虚引用释放堆外内存</li>
</ul>
</li>
<li>减少了一次数据拷贝，用户态与内核态的切换次数没有减少</li>
</ul>
<p>进一步优化（底层采用了 linux 2.1 后提供的 sendFile 方法），java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422193512663.png" alt="image-20220422193512663"></p>
<ol>
<li>java 调用 transferTo 方法后，要从 java 程序的<strong>用户态</strong>切换至<strong>内核态</strong>，使用 DMA将数据读入<strong>内核缓冲区</strong>，不会使用 cpu</li>
<li>数据从<strong>内核缓冲区</strong>传输到 <strong>socket 缓冲区</strong>，cpu 会参与拷贝</li>
<li>最后使用 DMA 将 <strong>socket 缓冲区</strong>的数据写入网卡，不会使用 cpu</li>
</ol>
<p>可以看到</p>
<ul>
<li>只发生了一次用户态与内核态的切换</li>
<li>数据拷贝了 3 次</li>
</ul>
<p>进一步优化（linux 2.4）</p>
<p><img src="https://zqyhdm-blog.oss-cn-shanghai.aliyuncs.com/markdown/image-20220422200545550.png" alt="image-20220422200545550"></p>
<ol>
<li>java 调用 transferTo 方法后，要从 java 程序的<strong>用户态</strong>切换至<strong>内核态</strong>，使用 DMA将数据读入<strong>内核缓冲区</strong>，不会使用 cpu</li>
<li>只会将一些 offset 和 length 信息拷入 <strong>socket 缓冲区</strong>，几乎无消耗</li>
<li>使用 DMA 将 <strong>内核缓冲区</strong>的数据写入网卡，不会使用 cpu</li>
</ol>
<p>整个过程仅只发生了一次用户态与内核态的切换，数据拷贝了 2 次。所谓的【零拷贝】，并不是真正无拷贝，而是在不会拷贝重复数据到 jvm 内存中，零拷贝的优点有</p>
<ul>
<li>更少的用户态与内核态的切换</li>
<li>不利用 cpu 计算，减少 cpu 缓存伪共享</li>
<li>零拷贝适合小文件传输</li>
</ul>
<h3 id="5-4-AIO"><a href="#5-4-AIO" class="headerlink" title="5.4 AIO"></a>5.4 AIO</h3><p>AIO 用来解决数据复制阶段的阻塞问题</p>
<ul>
<li>同步意味着，在进行读写操作时，线程需要等待结果，还是相当于闲置</li>
<li>异步意味着，在进行读写操作时，线程不必等待结果，而是将来由操作系统来通过回调方式由另外的线程来获得结果</li>
</ul>
<blockquote>
<p>异步模型需要底层操作系统（Kernel）提供支持</p>
<ul>
<li>Windows 系统通过 IOCP 实现了真正的异步 IO</li>
<li>Linux 系统异步 IO 在 2.6 版本引入，但其底层实现还是用多路复用模拟了异步 IO，性能没有优势</li>
</ul>
</blockquote>
<h4 id="文件-AIO"><a href="#文件-AIO" class="headerlink" title="文件 AIO"></a>文件 AIO</h4><p>AsynchronousFileChannel</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AioDemo1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            <span class="type">AsynchronousFileChannel</span> <span class="variable">s</span> <span class="operator">=</span> </span><br><span class="line">                AsynchronousFileChannel.open(</span><br><span class="line">                	Paths.get(<span class="string">&quot;1.txt&quot;</span>), StandardOpenOption.READ);</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">2</span>);</span><br><span class="line">            log.debug(<span class="string">&quot;read begin...&quot;</span>);</span><br><span class="line">            <span class="comment">//参数1 bytebuffer;参数2 读取的起始位置；参数3附件；参数4回调对象</span></span><br><span class="line">            s.read(buffer, <span class="number">0</span>, <span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span><span class="comment">//read成功</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;read completed...&#123;&#125;&quot;</span>, result);</span><br><span class="line">                    attachment.flip();</span><br><span class="line">                    debug(attachment);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="meta">@Override</span><span class="comment">//read失败</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;read failed...&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        log.debug(<span class="string">&quot;do other things...&quot;</span>);</span><br><span class="line">        System.in.read();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">13:44:56 [DEBUG] [main] c.i.aio.AioDemo1 - begin...</span><br><span class="line">13:44:56 [DEBUG] [main] c.i.aio.AioDemo1 - do other things...</span><br><span class="line">13:44:56 [DEBUG] [Thread-5] c.i.aio.AioDemo1 - read completed...2</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 0d                                           |a.              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br></pre></td></tr></table></figure>

<p>可以看到</p>
<ul>
<li>响应文件读取成功的是另一个线程 Thread-5</li>
<li>主线程并没有 IO 操作阻塞</li>
</ul>
<p>默认文件 AIO 使用的线程都是<strong>守护线程</strong>，所以最后要执行 <code>System.in.read()</code> 以避免守护线程意外结束</p>
<h4 id="网络-AIO"><a href="#网络-AIO" class="headerlink" title="网络 AIO"></a>网络 AIO</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AioServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">AsynchronousServerSocketChannel</span> <span class="variable">ssc</span> <span class="operator">=</span> AsynchronousServerSocketChannel.open();</span><br><span class="line">        ssc.bind(<span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(<span class="number">8080</span>));</span><br><span class="line">        ssc.accept(<span class="literal">null</span>, <span class="keyword">new</span> <span class="title class_">AcceptHandler</span>(ssc));</span><br><span class="line">        System.in.read();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">closeChannel</span><span class="params">(AsynchronousSocketChannel sc)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            System.out.printf(<span class="string">&quot;[%s] %s close\n&quot;</span>, Thread.currentThread().getName(), sc.getRemoteAddress());</span><br><span class="line">            sc.close();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ReadHandler</span> <span class="keyword">implements</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsynchronousSocketChannel sc;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">ReadHandler</span><span class="params">(AsynchronousSocketChannel sc)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.sc = sc;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (result == -<span class="number">1</span>) &#123;</span><br><span class="line">                    closeChannel(sc);</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                System.out.printf(<span class="string">&quot;[%s] %s read\n&quot;</span>, Thread.currentThread().getName(), sc.getRemoteAddress());</span><br><span class="line">                attachment.flip();</span><br><span class="line">                System.out.println(Charset.defaultCharset().decode(attachment));</span><br><span class="line">                attachment.clear();</span><br><span class="line">                <span class="comment">// 处理完第一个 read 时，需要再次调用 read 方法来处理下一个 read 事件</span></span><br><span class="line">                sc.read(attachment, attachment, <span class="built_in">this</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            closeChannel(sc);</span><br><span class="line">            exc.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">WriteHandler</span> <span class="keyword">implements</span> <span class="title class_">CompletionHandler</span>&lt;Integer, ByteBuffer&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsynchronousSocketChannel sc;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="title function_">WriteHandler</span><span class="params">(AsynchronousSocketChannel sc)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.sc = sc;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(Integer result, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            <span class="comment">// 如果作为附件的 buffer 还有内容，需要再次 write 写出剩余内容</span></span><br><span class="line">            <span class="keyword">if</span> (attachment.hasRemaining()) &#123;</span><br><span class="line">                sc.write(attachment);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> &#123;</span><br><span class="line">            exc.printStackTrace();</span><br><span class="line">            closeChannel(sc);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">AcceptHandler</span> <span class="keyword">implements</span> <span class="title class_">CompletionHandler</span>&lt;AsynchronousSocketChannel, Object&gt; &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> AsynchronousServerSocketChannel ssc;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">AcceptHandler</span><span class="params">(AsynchronousServerSocketChannel ssc)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.ssc = ssc;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">completed</span><span class="params">(AsynchronousSocketChannel sc, Object attachment)</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.printf(<span class="string">&quot;[%s] %s connected\n&quot;</span>, Thread.currentThread().getName(), sc.getRemoteAddress());</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> ByteBuffer.allocate(<span class="number">16</span>);</span><br><span class="line">            <span class="comment">// 读事件由 ReadHandler 处理</span></span><br><span class="line">            sc.read(buffer, buffer, <span class="keyword">new</span> <span class="title class_">ReadHandler</span>(sc));</span><br><span class="line">            <span class="comment">// 写事件由 WriteHandler 处理</span></span><br><span class="line">            sc.write(Charset.defaultCharset().encode(<span class="string">&quot;server hello!&quot;</span>), ByteBuffer.allocate(<span class="number">16</span>), <span class="keyword">new</span> <span class="title class_">WriteHandler</span>(sc));</span><br><span class="line">            <span class="comment">// 处理完第一个 accpet 时，需要再次调用 accept 方法来处理下一个 accept 事件</span></span><br><span class="line">            ssc.accept(<span class="literal">null</span>, <span class="built_in">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">failed</span><span class="params">(Throwable exc, Object attachment)</span> &#123;</span><br><span class="line">            exc.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

      
    </div>
    <div class="article-footer">
      <blockquote class="mt-2x">
  <ul class="post-copyright list-unstyled">
    
    <li class="post-copyright-link hidden-xs">
      <strong>本文链接：</strong>
      <a href="https://skyung.gitee.io/Skyung/2022/04/24/NIO/" title="NIO" target="_blank" rel="external">https://skyung.gitee.io/Skyung/2022/04/24/NIO/</a>
    </li>
    
    <li class="post-copyright-license">
      <strong>版权声明： </strong> 本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！
    </li>
  </ul>
</blockquote>


<div class="panel panel-default panel-badger">
  <div class="panel-body">
    <figure class="media">
      <div class="media-left">
        <a href="https://github.com/cofess" target="_blank" class="img-burn thumb-sm visible-lg">
          <img src="/Skyung/images/avatar.jpg" class="img-rounded w-full" alt="">
        </a>
      </div>
      <div class="media-body">
        <h3 class="media-heading"><a href="https://github.com/cofess" target="_blank"><span class="text-dark">Skyung</span><small class="ml-1x">Web Developer</small></a></h3>
        <div>脚踏实地，仰望星空</div>
      </div>
    </figure>
  </div>
</div>


    </div>
  </article>
  
    
  <section id="comments">
  	
      <div id="uyan_frame"></div>
    
  </section>


  
</div>

  <nav class="bar bar-footer clearfix" data-stick-bottom>
  <div class="bar-inner">
  
  <ul class="pager pull-left">
    
    <li class="prev">
      <a href="/Skyung/2022/05/01/netty%E8%81%8A%E5%A4%A9%E5%AE%A4/" title="Netty聊天室"><i class="icon icon-angle-left" aria-hidden="true"></i><span>&nbsp;&nbsp;上一篇</span></a>
    </li>
    
    
    <li class="next">
      <a href="/Skyung/2022/04/21/%E5%86%85%E9%83%A8%E7%B1%BB/" title="(no title)"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a>
    </li>
    
    
    <li class="toggle-toc">
      <a class="toggle-btn " data-toggle="collapse" href="#collapseToc" aria-expanded="false" title="文章目录" role="button">    <span>[&nbsp;</span><span>文章目录</span>
        <i class="text-collapsed icon icon-anchor"></i>
        <i class="text-in icon icon-close"></i>
        <span>]</span>
      </a>
    </li>
    
  </ul>
  
  
  <!-- Button trigger modal -->
  <button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button>
  <!-- <div class="wave-icon wave-icon-danger btn-donate" data-toggle="modal" data-target="#donateModal">
    <div class="wave-circle"><span class="icon"><i class="icon icon-bill"></i></span></div>
  </div> -->
  
  
  <div class="bar-right">
    
    <div class="share-component" data-sites="weibo,qq,wechat,facebook,twitter" data-mobile-sites="weibo,qq,qzone"></div>
    
  </div>
  </div>
</nav>
  
<!-- Modal -->
<div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content donate">
      <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      <div class="modal-body">
        <div class="donate-box">
          <div class="donate-head">
            <p>感谢您的支持，我会继续努力的!</p>
          </div>
          <div class="tab-content">
            <div role="tabpanel" class="tab-pane fade active in" id="alipay">
              <div class="donate-payimg">
                <img src="/Skyung/images/donate/alipay.jpg" alt="扫码支持" title="扫一扫" />
              </div>
              <p class="text-muted mv">扫码打赏，你说多少就多少</p>
              <p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p>
            </div>
            <div role="tabpanel" class="tab-pane fade" id="wechatpay">
              <div class="donate-payimg">
                <img src="/Skyung/images/donate/wechat.jpg" alt="扫码支持" title="扫一扫" />
              </div>
              <p class="text-muted mv">扫码打赏，你说多少就多少</p>
              <p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p>
            </div>
          </div>
          <div class="donate-footer">
            <ul class="nav nav-tabs nav-justified" role="tablist">
              <li role="presentation" class="active">
                <a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a>
              </li>
              <li role="presentation" class="">
                <a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a>
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>



</main>

  <footer class="footer" itemscope itemtype="http://schema.org/WPFooter">
	
	
    <ul class="social-links">
    	
        <li><a href="/Skyung/null" target="_blank" title="Github" data-toggle=tooltip data-placement=top><i class="icon icon-github"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Weibo" data-toggle=tooltip data-placement=top><i class="icon icon-weibo"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Twitter" data-toggle=tooltip data-placement=top><i class="icon icon-twitter"></i></a></li>
        
        <li><a href="/Skyung/null" target="_blank" title="Behance" data-toggle=tooltip data-placement=top><i class="icon icon-behance"></i></a></li>
        
        <li><a href="/Skyung/atom.xml" target="_blank" title="Rss" data-toggle=tooltip data-placement=top><i class="icon icon-rss"></i></a></li>
        
    </ul>

    <div class="copyright">
    	
        <div class="publishby">
        	Theme by <a href="https://github.com/cofess" target="_blank"> cofess </a>base on <a href="https://github.com/cofess/hexo-theme-pure" target="_blank">pure</a>.
        </div>
    </div>
</footer>
  <script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<script>
window.jQuery || document.write('<script src="js/jquery.min.js"><\/script>')
</script>

<script src="/Skyung/js/plugin.min.js"></script>


<script src="/Skyung/js/application.js"></script>


    <script>
(function (window) {
    var INSIGHT_CONFIG = {
        TRANSLATION: {
            POSTS: '文章',
            PAGES: '页面',
            CATEGORIES: '分类',
            TAGS: '标签',
            UNTITLED: '(未命名)',
        },
        ROOT_URL: '/Skyung/',
        CONTENT_URL: '/Skyung/content.json',
    };
    window.INSIGHT_CONFIG = INSIGHT_CONFIG;
})(window);
</script>

<script src="/Skyung/js/insight.js"></script>






   




   
    
    <script defer type="text/javascript" src="http://v2.uyan.cc/code/uyan.js?uid=[object Object]"></script>








</body>
</html>